
/*
        process.c
        
        Macros, structs and methods used for process switch
        
        Meng Sun(c) leon.meng.sun@gmail.com
        Source code released under LGPLv3
        https://www.gnu.org/licenses/lgpl-3.0.txt
*/

#include "process.h"

private ProcessDescriptor pdKernel;

private ProcessDescriptor* pdRunning = NULL;

private ProcessDescriptor* pdReadyListHead = NULL;

private ProcessDescriptor* pdBlockedListHead = NULL;

private ProcessDescriptor* pdTombListHead = NULL;

static void callback procProcessSyscallISRCallback(reg32b cause, uint32* fp, SyscallDescriptor* scd, SyscallReturnDescriptor* scrd){
	
	if ((scd == NULL) || (scrd == NULL)) return;
	
	switch (scd->cmd){

		case SYSCMDEXECFROMMEM: {
				ProcessDescriptor* pd = procExecProcessFromMemory(scd->payload.execFromMem.src, 
											scd->payload.execFromMem.size, 
											scd->payload.execFromMem.priority, 
											scd->payload.execFromMem.parentProcID);
				if (pd == NULL){
					scrd->ifSuccess = FALSE;
					scrd->payload.execFromMem.pdPtr = NULL;
				} else {
					scrd->ifSuccess = TRUE;
					scrd->payload.execFromMem.pdPtr = (void*)pd;
					scrd->payload.execFromMem.processID = pd->processID;
					procScheduler(fp);
				}
			}
		
			break;
			
		case SYSCMDKILLPROCESS: {
				sint8 retVal = procKillProcess((ProcessDescriptor*)(scd->payload.killProcess.pd), fp);
				if (retVal == 0){
					scrd->ifSuccess = TRUE;
					scrd->payload.killProcessRetVal = 0;
				} else {
					scrd->ifSuccess = FALSE;
					scrd->payload.killProcessRetVal = retVal;
				}
			}
			break;

		default:
			scrd->ifSuccess = FALSE;
			break;
	};
	
	return;
}

public sint8 procRemovePDFromList(ProcessDescriptor* pd, ProcessDescriptor** pdp){

	if (pd == NULL) return -1;
	
	if ((pd->prev == NULL) && (pd->next == NULL)){ /*solo obj*/
		(*pdp) == NULL;
	} else if ((pd->prev != NULL) && (pd->next == NULL)){ /*tail obj*/
		pd->prev->next = NULL;
		pd->prev = NULL;
	} else if ((pd->prev == NULL) && (pd->next != NULL)){ /*head obj*/
		pd->next->prev = NULL;
		(*pdp) = pd->next;
		pd->next = NULL;
	} else{ /*obj-in-the-middle*/
		pd->prev->next = pd->next;
		pd->next->prev = pd->prev;
		pd->next = NULL;
		pd->prev = NULL;
	}
	
	return 0;
}

public sint8 procAppendPDToList(ProcessDescriptor* pd, ProcessDescriptor** pdp){
	
	if (pd == NULL) return -1;
	
	if ((*pdp) == NULL){
		pd->prev = NULL;
		pd->next = NULL;
		(*pdp) = pd;
	}else{
		pd->prev = NULL;
		pd->next = (*pdp);
		(*pdp)->prev = pd;
		(*pdp) = pd;
	}
	
	return 0;
}

public sint8 procByteFiller(byte* start, uint32 size, byte pattern){
	
	uint32 index;
	
	if (start == NULL) return -1;
	
	for (index = 0; index < size; ++index){
		start[index] = pattern;
	}
	
	return 0;
}

public sint8 procWordFiller(uint32* start, uint32 size, uint32 pattern){
	
	uint32 index;
	
	if (start == NULL) return -1;
	
	for (index = 0; index < size; ++index){
		start[index] = pattern;
	}
	
	return 0;
}

public MemoryPageDescriptor* procMemoryMap(ProcessDescriptor* pd, byte* errorAddr){
	
	TLBItem tlbItem;
	byte* errorAddr8kPage = NULL;
	MemoryPageDescriptor* freePageIndex = NULL;
	MemoryPageDescriptor* mpdResidual = NULL;
	
	if (pd == NULL) return NULL;
	
	mpdResidual = (MemoryPageDescriptor*)mmSysMAlloc(sizeof(MemoryPageDescriptor));
	if (mpdResidual == NULL) return NULL;
	
	freePageIndex = *(mmGetSystemFreeMPDListHeadPtr());
	
	/*1.1 check if physical page has space, alloc 1*/
	if (freePageIndex == NULL){
		mmSysMFree((void*)mpdResidual);
		return NULL;
	}
	do{
		if (freePageIndex->size >= 0x1){ /*free phy page found*/
			if (freePageIndex->size > 0x1){ /*free phy page has more than 1 8k slot*/
			
				/*1.1A.1 construct residual MPD if needed*/
				mpdResidual->pmaStart = freePageIndex->pmaStart + 0x2000;
				mpdResidual->vmaStart = NULL;
				mpdResidual->size = freePageIndex->size - 0x1;
				mpdResidual->prev = NULL;
				mpdResidual->next = NULL;
				
				freePageIndex->size = 0x1;
			} else { /*free phy page has only 1 8k slot so mpdResidual->size == 0*/
			
				/*1.1B.1 set mpdResidual->size == 0*/
				mpdResidual->size = 0;
			}
			
			/*1.2 remove freePageIndex from mpdFreeListHead*/
			mmRemoveMPDFromList(freePageIndex, mmGetSystemFreeMPDListHeadPtr());
			
			/*1.3 attach mpdResidual to mpdFreeListHead, if size not 0*/
			if (mpdResidual->size != 0) mmAppendMPDToList(mpdResidual, mmGetSystemFreeMPDListHeadPtr());
			
			/*1.4 attach freePageIndex to pd->mpdListHead*/
			mmAppendMPDToList(freePageIndex, &(pd->mpdListHead));
			
			/*1.5 find vma page address needed to be allocated*/
			errorAddr8kPage = (byte*)((uint32)errorAddr & 0xffffe000);
			freePageIndex->vmaStart = errorAddr8kPage;
			
			/*1.6 construct TLB item from freeMPDIndex*/
			if (((uint32)errorAddr8kPage & 0xe0000000) == 0xe0000000){ /*kernel space 0xe0000000-0xffffffff*/
				mmGetTLBItemFromMemoryPageDescriptor(&tlbItem, freePageIndex, 0x0, MCTCACHEABLE, MGTGLOBAL, MWTREADWRITE, MVTVALID);
			} else { /*user space 0x00000000-0x7fffffff and supervisor space 0xc0000000-0xdfffffff*/
				mmGetTLBItemFromMemoryPageDescriptor(&tlbItem, freePageIndex, pd->processID, MCTCACHEABLE, MGTLOCAL, MWTREADWRITE, MVTVALID);
			}
			WriteTLBItemByGivenIndex(mmGetNextTLBTableIndex(), tlbItem);
			
			if (mpdResidual->size == 0) mmSysMFree((void*)mpdResidual);
			return freePageIndex;
			
		} else { /*free phy page not found*/
			freePageIndex = freePageIndex->next;
		}
	}while(freePageIndex != NULL);
	
	mmSysMFree((void*)mpdResidual);
	return NULL; /*no pages available*/
}

public sint8 procMemoryUnmap(ProcessDescriptor* pd, MemoryPageDescriptor* mpd){
	
	reg32b tlbEntryHiReg = 0;
	reg32b tlbIndexReg = 0;
	TLBItem tlbNullRef;
	
	if ((mpd == NULL) || (pd == NULL)) return -1;
	
	/*1.1 check if tlb table has mpd item, if has, remove by writing null reference*/
	tlbEntryHiReg = (uint32)(mpd->vmaStart) & 0xffffe000;
	ProbeTLBItemByEntryHi(tlbEntryHiReg, tlbIndexReg);
	if ((tlbIndexReg & 0x80000000) == 0x00000000){ /*tlb match found*/
		tlbNullRef.entryHi = KSEG0BASEADDR;
		tlbNullRef.entryLo0 = 0x0;
		tlbNullRef.entryLo1 = 0x0;
		tlbNullRef.pageMask = 0x0;
		WriteTLBItemByGivenIndex(tlbIndexReg, tlbNullRef);
	}
	
	/*1.2 remove mpd from pd->mpdListHead*/
	mmRemoveMPDFromList(mpd, &(pd->mpdListHead));
	
	/*1.3 append mpd to mpdFreeListHead and call mmCleanMPDFreeList*/
	mpd->vmaStart = NULL;
	mmAppendMPDToList(mpd, mmGetSystemFreeMPDListHeadPtr());
	mmCleanMPDFreeList();
	
	return 0;
}

public sint8 procPDInit(ProcessDescriptor* pd, uint8 processID, uint8 parentProcessID, uint8 priority){

	uint8 index = 0;

	if (pd == NULL) return -1;
	
	pd->processID = processID;
	pd->parentProcessID = parentProcessID;
	pd->originPriority = priority;
	pd->modifiedPriority = priority;
	
	for (index = 0; index < 32; ++index){
		pd->gpr[index] = 0x0;
	}
	
	pd->cp0EPC = 0x0;
	pd->cp0ErrorPC = 0x0;
	pd->cp0Status = 0x0040ff00; /*kernel mode, if 0x0040ff10 -> user mode*/
	
	pd->mpdListHead = NULL;
	
	pd->dodFreeListHead = NULL;
	pd->dodUsedListHead = NULL;
	
	pd->prev = NULL;
	pd->next = NULL;
	
	return 0;
}

private void callback procTLBLoadStoreISRCallback(reg8b excCode, reg32b cp0Context, reg32b cp0EntryHi){

	reg32b cp0Index = 0x0;
	
	/*1.1 probe cp0 index and see if tlb entry is there*/
	ProbeTLBItemByEntryHi((cp0EntryHi & 0xffffe000), cp0Index);
	if ((cp0Index & 0x80000000) == 0x80000000){ /*no match -> tlb refill*/
	
		/*1.1A.1 check if tlbl or tlbs*/
		if (excCode == 0x02){ /*tlbl -> load or fetch*/
		
			/*1.1A.1A.1 illegal status, process should be noticed to terminate*/
		}else if(excCode == 0x03){ /*tlbs -> store*/
		
			ProcessDescriptor* pdAffected = NULL;
			MemoryPageDescriptor* mpdIndex = NULL;
			TLBItem tlbItem;
		
			/*1.1A.1B.1 check if kernel space or user/supervisor space*/
			if ((cp0EntryHi & 0xe0000000) == 0xe0000000){ /*kernel space 0xe0000000-0xffffffff*/
				pdAffected = &pdKernel;
			} else { /*user/supervisor space 0xc0000000-0xdfffffff/0x00000000-0x7fffffff*/
				pdAffected = pdRunning;
			}
			
			/*1.1A.1B.2 check if page has been allocated*/
			if (pdAffected == NULL) return;
			mpdIndex = pdAffected->mpdListHead;
			while(mpdIndex != NULL){
				if ((uint32)(mpdIndex->vmaStart) == (cp0EntryHi & 0xffffe000)){ /*page has been allocated*/
					if ((cp0EntryHi & 0xe0000000) == 0xe0000000){ /*kernel space*/
						mmGetTLBItemFromMemoryPageDescriptor(&tlbItem, mpdIndex, 0x0, MCTCACHEABLE, MGTGLOBAL, MWTREADWRITE, MVTVALID);
					} else { /*user space*/
						mmGetTLBItemFromMemoryPageDescriptor(&tlbItem, mpdIndex, pdAffected->processID, MCTCACHEABLE, MGTLOCAL, MWTREADWRITE, MVTVALID);
					}
					WriteTLBItemByGivenIndex(mmGetNextTLBTableIndex(), tlbItem);
					return;
				}
				mpdIndex = mpdIndex->next;
			}
			
			/*1.1A.1B.3 page not allocated, create one*/
			procMemoryMap(pdAffected, (byte*)(cp0EntryHi & 0xffffe000));
			
			return;
		}
	} else { /*match -> tlb invalid*/
	
		/*at this moment illegal since no swap neither disk<->memory map is used*/
	}

	return;
}

public sint8 procInit(void){

	DynamicObjectDescriptor dodInit = {(byte*)(0xe0000000 + sizeof(DynamicObjectDescriptor)), 0x2000000 - sizeof(DynamicObjectDescriptor), NULL, NULL};
	
	setTLBLoadStoreISRCallback(procTLBLoadStoreISRCallback);
	setProcSyscallISRCallback(procProcessSyscallISRCallback);

	/*1.1 initialize pd*/
	procPDInit(&pdKernel, 0x0, 0x0, 0xff);
	
	/*1.2 copy code and data to 0xe0000000, if any*/
	
	/*1.3 construct dod for dynamic data area*/
	mmCopyData((byte*)(&dodInit), (byte*)0xe0000000, sizeof(DynamicObjectDescriptor), FALSE);
	
	/*1.4 link dod for dynamic data area*/
	mmAppendDODToList((DynamicObjectDescriptor*)0xe0000000, &(pdKernel.dodFreeListHead));
	
	return 0;
}

static void procProcessSwitch(ProcessDescriptor* oldPD, ProcessDescriptor* newPD, uint32* fp){
	
	uint8 index = 0;
	
	if ((newPD == NULL) || (fp == NULL)) return;
	
	/*1.1 save gpr from oldPD if not NULL*/
	if (oldPD != NULL){
		for (index = 0; index < 32; ++index){
			oldPD->gpr[index] = fp[GETIDXFROMFP(index)];
		}
		oldPD->cp0EPC = fp[GETIDXFROMFP(32)];
		oldPD->cp0ErrorPC = fp[GETIDXFROMFP(33)];
		oldPD->cp0Status = fp[GETIDXFROMFP(31)];
	} else { /*only valid when the first custom process to be run*/
		for (index = 0; index < 32; ++index){
			pdKernel.gpr[index] = fp[GETIDXFROMFP(index)];
		}
		pdKernel.cp0EPC = fp[GETIDXFROMFP(32)];
		pdKernel.cp0ErrorPC = fp[GETIDXFROMFP(33)];
		pdKernel.cp0Status = fp[GETIDXFROMFP(31)];
	}

	/*1.2 copy newPD to gpr*/
	for (index = 0; index < 32; ++index){
		fp[GETIDXFROMFP(index)] = newPD->gpr[index];
	}
	fp[GETIDXFROMFP(32)] = newPD->cp0EPC;
	fp[GETIDXFROMFP(33)] = newPD->cp0ErrorPC;
	fp[GETIDXFROMFP(31)] = newPD->cp0Status;
	
	/*1.3 move oldPD to pdReadyListHead*/
	if (oldPD != NULL){
		procAppendPDToList(oldPD, &(pdReadyListHead));
	}
	
	/*1.4 move newPD as pdRunning*/
	pdRunning = newPD;
	procRemovePDFromList(newPD, &(pdReadyListHead));
	
	/*1.5 update EntryHi ASID field*/
	MacroSetCurrentASID(newPD->processID);
	
	return;
}

public void procScheduler(uint32* fp){

	if (fp == NULL) return;
	
	ProcessDescriptor* pdIndex = pdReadyListHead;
	ProcessDescriptor* pdHighestPriority = pdReadyListHead;
	
	if (pdIndex == NULL) return; /*no need to do process schedule*/
	
	/*1.1 get highest priority process from ready list*/
	do{
		if (pdHighestPriority->modifiedPriority > pdIndex->modifiedPriority){ /*priority 0 has the highest priority*/
			pdHighestPriority = pdIndex;
		}
		pdIndex = pdIndex->next;
	}while(pdIndex != NULL);
	
	/*1.2 compare priority between highest process in ready list and running process*/
	if (pdRunning != NULL){ /*there is a process already running*/
		if (pdHighestPriority->modifiedPriority < pdRunning->modifiedPriority){ /*there is a process in ready list should be run*/
			procProcessSwitch(pdRunning, pdHighestPriority, fp);
		}
	} else { /*there is no process running*/
		procProcessSwitch(NULL, pdHighestPriority, fp);
	}
	
	return;
}

public ProcessDescriptor* procExecProcessFromMemory(byte* src, uint32 size, uint8 priority, uint8 parentProcID){

	ProcessDescriptor* pd = NULL;
	DynamicObjectDescriptor* dodData = NULL;
	ProcessDescriptor* pdCurrentRunning = NULL;
	reg32b currentASIDWhenNull;
	
	/*1.1 create process descriptor*/
	pd = (ProcessDescriptor*)mmSysMAlloc(sizeof(ProcessDescriptor));
	
	if (pd == NULL) return NULL;
	
	/*1.2 create dod for data heap*/
	dodData = (DynamicObjectDescriptor*)mmSysMAlloc(sizeof(DynamicObjectDescriptor));
	
	if (dodData == NULL){
		mmSysMFree((void*)pd);
		return NULL;
	}
	
	/*1.3 init pd and dod*/
	procPDInit(pd, priority, parentProcID, priority);
	dodData->start = (byte*)((0x00000000 + size + 0x2000) & 0xffffe000 + sizeof(DynamicObjectDescriptor));
	dodData->size = 0x80000000 - ((0x00000000 + size + 0x2000) & 0xffffe000) - 0x1000000 - sizeof(DynamicObjectDescriptor); /*stack size 16MB*/
	dodData->prev = NULL;
	dodData->next = NULL;
	
	/*1.4 save and replace current running process*/
	MacroGetCurrentASID(currentASIDWhenNull); /*this is used when pdRunning is NULL*/
	pdCurrentRunning = pdRunning;
	pdRunning = pd;
	MacroSetCurrentASID(pd->processID);
	
	/*1.5 copy text and data*/
	mmCopyData(src, (byte*)0x00000000, size, FALSE);
	
	/*1.6 copy dodData*/
	mmCopyData((byte*)dodData, (byte*)((0x00000000 + size + 0x2000) & 0xffffe000), sizeof(DynamicObjectDescriptor), FALSE);
	
	/*1.7 link dodData*/
	pd->dodFreeListHead = (DynamicObjectDescriptor*)((0x00000000 + size + 0x2000) & 0xffffe000);
	
	/*1.8 restore running process*/
	pdRunning = pdCurrentRunning;
	if (pdRunning != NULL){
		MacroSetCurrentASID(pdRunning->processID);
	} else {
		MacroSetCurrentASID(currentASIDWhenNull);
	}
	
	/*1.9 append pd to pdReadyListHead*/
	procAppendPDToList(pd, &(pdReadyListHead));
	
	return pd;

}

public sint8 procKillProcess(ProcessDescriptor* pd, uint32* fp){

	MemoryPageDescriptor* mpdIndex = NULL;

	if ((pd == NULL) || (fp == NULL)) return -1;
	
	if (pd == pdRunning){ /*pd is current running now*/
	
		ProcessDescriptor* pdIndex = pdReadyListHead;
		ProcessDescriptor* pdHighestPriority = pdIndex;

		/*1.1 move terminating pd to pdTombListHead*/
		if (pdIndex != NULL){ /*there are some process on ready list*/
		
			/*1A.1 get hightest priority process from pdReadyList*/
			do{
				if (pdIndex->modifiedPriority < pdHighestPriority->modifiedPriority){
					pdHighestPriority = pdIndex;
				}
				pdIndex = pdIndex->next;
			}while(pdIndex != NULL);
			
			/*1A.2 switch pdRunning with pdReady*/
			procProcessSwitch(pdRunning, pdHighestPriority, fp);
			
			/*1A.3 move pd to pdTombListHead*/
			procRemovePDFromList(pd, &(pdReadyListHead));
			procAppendPDToList(pd, &(pdTombListHead));
			
		} else { /*no process on ready list, so kernel pd will be used*/
		
			/*1B.1 switch pdKernel on foreground*/
			procProcessSwitch(pdRunning, &pdKernel, fp);
			
			/*1B.2 move pd to pdTombListHead*/
			procRemovePDFromList(pd, &(pdReadyListHead));
			procAppendPDToList(pd, &(pdTombListHead));
		}
	} else { /*pd is now in ReadyList or BlockList*/
	
		ProcessDescriptor* pdIndex = NULL;
	
		/*2.1 check pd in ReadyList or BlockList*/
		pdIndex = pd;
		while(pdIndex->prev != NULL) pdIndex = pdIndex->prev;
		
		/*2.2 remove pd from ReadyList or BlockList*/
		if (pdIndex == pdReadyListHead){
			procRemovePDFromList(pd, &(pdReadyListHead));
		} else if (pdIndex == pdBlockedListHead){
			procRemovePDFromList(pd, &(pdBlockedListHead));
			/*?also notice device driver maybe?*/
		}
		
		/*2.3 move pd to pdTombListHead*/
		procAppendPDToList(pd, &(pdTombListHead));
	}
	
	/*3.1 remove pd from pdTombListHead*/
	procRemovePDFromList(pd, &(pdTombListHead));
	
	/*3.2 free->unmap all pd->mpdListHead*/
	mpdIndex = pd->mpdListHead;
	while(mpdIndex != NULL){
		MemoryPageDescriptor* mpdNext = mpdIndex->next;
		procMemoryUnmap(pd, mpdIndex);
		mpdIndex = mpdNext;
	}
	
	/*3.3 free pd*/
	mmSysMFree((void*)pd);
	
	return 0;
}

